﻿namespace NJQuery {
	using System;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// A complex type representing a value that is either an unsigned absolute value range or a signed relative value range from a logical reference point.
	/// </summary>
	[TypeConverter(typeof(RelativeValueRangeConverter))]
	public struct RelativeValueRange : IEquatable<RelativeValueRange>, IComplexProperty {


		#region Static Methods

		/// <summary>
		/// Converts a string representation to its <see cref="RelativeValueRange"/> equivalent.
		/// </summary>
		/// <param name="text">A <see cref="String"/> containing the value to convert.</param>
		/// <param name="result">The parsed value when the conversion succeeds, or a default instance otherwise.</param>
		/// <returns>Indicates whether the conversion succeeded.</returns>
		public static bool TryParse(string text, out RelativeValueRange result) {

			if(string.IsNullOrEmpty(text) || !text.Contains(":")) {
				result = new RelativeValueRange();
				return false;
			}

			string[] splitText = text.Split(':');
			if(splitText.Length != 2) {
				result = new RelativeValueRange();
				return false;
			}

			RelativeValue minValue; RelativeValue maxValue;
			if(RelativeValue.TryParse(splitText[0].Trim(), out minValue) && RelativeValue.TryParse(splitText[1].Trim(), out maxValue)) {
				result = new RelativeValueRange(minValue.Relative, minValue.Value, maxValue.Value);
				return true;
			}

			result = new RelativeValueRange();
			return false;
		}

		#endregion


		#region Fields

		private readonly int minValue;
		private readonly int maxValue;
		private readonly bool relative;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes an instance of the <see cref="RelativeValueRange"/> type.
		/// </summary>
		/// <param name="relative">If true the value provided is relative to a logical point of reference, otherwise the value provided is absolute and must be unsigned.</param>
		/// <param name="minValue">The minimum range value defined as relative or absolute by the preceding argument.</param>
		/// <param name="maxValue">The maximum range value defined as relative or absolute by the first argument.</param>
		/// <exception cref="ArgumentOutOfRangeException">Values cannot be negative unless specified as a relative value range.</exception>
		/// <exception cref="InvalidOperationException">The minimum value cannot be greater than the maximum value.</exception>
		public RelativeValueRange(bool relative, int minValue, int maxValue) {
			if(!relative) {
				if(minValue < 0) throw new ArgumentOutOfRangeException("minValue", minValue, "Minimum value cannot be negative unless specified as a relative value range.");
				if(maxValue < 0) throw new ArgumentOutOfRangeException("maxValue", maxValue, "Maximum value cannot be negative unless specified as a relative value range.");
			}

			if(maxValue < minValue) throw new InvalidOperationException("The minimum value cannot be greater than the maximum value.");

			this.minValue = minValue;
			this.maxValue = maxValue;
			this.relative = relative;
		}

		#endregion


		#region Properties

		/// <summary>
		/// Returns whether or not the contained value range is relative as opposed to absolute.
		/// </summary>
		public bool Relative {
			get { return relative; }
		}

		/// <summary>
		/// Returns the minimum value of the range, defined as either relative or absolute.
		/// </summary>
		public int MinValue {
			get { return minValue; }
		}

		/// <summary>
		/// Returns the maximum value of the range, defined as either relative or absolute.
		/// </summary>
		public int MaxValue {
			get { return maxValue; }
		}

		#endregion


		#region Methods: Object management

		/// <summary>
		/// Determines whether or not the specified object is equal to the current <see cref="RelativeValueRange"/> object.
		/// </summary>
		/// <param name="obj">The <see cref="Object"/> to compare.</param>
		/// <returns>Whether or not the provided object is equal.</returns>
		public override bool Equals(object obj) {
			if(obj is RelativeValueRange) return this == (RelativeValueRange)obj;
			return false;
		}

		/// <summary>
		/// Determines whether or not the specified RelativeValueRange is equal to the current <see cref="RelativeValueRange"/>.
		/// </summary>
		/// <param name="other">The <see cref="RelativeValueRange"/> to compare.</param>
		/// <returns>Whether or not the provided value is equal.</returns>
		public bool Equals(RelativeValueRange other) {
			return this == other;
		}

		/// <summary>
		/// Serves as a hash function for <see cref="RelativeValueRange"/>.
		/// </summary>
		/// <returns>The hash value associated with the contained data.</returns>
		public override int GetHashCode() {
			//TODO: Have TheMuuj look at this
			int hashCode = minValue + maxValue >> 4;
			if(relative) return ~hashCode;
			return hashCode;
		}

		/// <summary>
		/// Returns a <see cref="String"/> that represents the current <see cref="RelativeValueRange"/> object.
		/// </summary>
		/// <returns>The string representation.</returns>
		public override string ToString() {
			return string.Format(CultureInfo.InvariantCulture, "{0}:{1}", new RelativeValue(relative, minValue), new RelativeValue(relative, maxValue));
		}

		/// <summary>
		/// Returns a literal JavaScript value used to represent the server value on the client.
		/// </summary>
		/// <returns>The client script value.</returns>
		public string ToClientSource() {
			return ToString().ToClientSource();
		}

		#endregion


		#region Operators

		/// <summary>
		/// Compares the equality of two <see cref="RelativeValueRange"/> values.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are equal.</returns>
		public static bool operator ==(RelativeValueRange value0, RelativeValueRange value1) {
			return value0.relative == value1.relative && value0.minValue.Equals(value1.minValue) && value0.maxValue.Equals(value1.maxValue);
		}

		/// <summary>
		/// Compares the inequality of two <see cref="RelativeValueRange"/> values.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are distinct.</returns>
		public static bool operator !=(RelativeValueRange value0, RelativeValueRange value1) {
			return value0.relative != value1.relative || !value0.minValue.Equals(value1.minValue) || !value0.maxValue.Equals(value1.maxValue);
		}

		#endregion


	}

}